package com.compunit.composite;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import javax.imageio.ImageIO;

import org.compunit.Provide;
import org.compunit.interfaces.IComponent;
import org.compunit.interfaces.IResourceRetriever;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GLContext;

import com.compunit.composite.b.interfaces.IRenderer;

@Provide({IRenderer.class})
public class Renderer implements IRenderer {
	
	private Box box;
	private int stride;
	private URL url;
	private int textureID;

	@Override
	public void initScene() {
		// Create a box with data info in buffers.
		this.box = new Box(1, 1, 1);
		// Check support.
		if(!GLContext.getCapabilities().GL_ARB_vertex_buffer_object) throw new UnsupportedOperationException("Cannot use VBO.");
		this.initVertexVBO();
		this.initIndexVBO();
		this.initTexture();
	}
	
	private void initVertexVBO() {
		// Create VBO for interleaved array.
		final IntBuffer index = BufferUtils.createIntBuffer(1);
		GL15.glGenBuffers(index);
		this.box.setInterleavedVBO(index.get(0));
		
		// Send in interleaved data as static, assuming the object is static.
		// Bind interleaved VBO.
		GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, this.box.getInterleavedVBO());
		// Send interleaved data to card.
		GL15.glBufferData(GL15.GL_ARRAY_BUFFER, this.box.getInterleavedBuffer(), GL15.GL_STATIC_DRAW);
		
		// Calculate stride.
		// Data is in format of <VxVyVzNxNyNzUV>, with each being a 4 byte float. Total would be 4 * 8 = 32.
		this.stride = 32;
		
		// Enable client state.
		GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
		GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY);
		GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
		
		// Set vertex VBO pointer. Vertex is the leading one, so offset is 0.
		GL11.glVertexPointer(3, GL11.GL_FLOAT, this.stride, 0);
		// Set normal VBO pointer. Normal is after 3 4-byte vertex floats, so offset is 3 * 4 = 12.
		GL11.glNormalPointer(GL11.GL_FLOAT, this.stride, 12);
		// Set texture coordnate pointer.
		GL11.glTexCoordPointer(2, GL11.GL_FLOAT, this.stride, 24);
	}
	
	private void initIndexVBO() {
		// Create VBO for indices.
		final IntBuffer indexID = BufferUtils.createIntBuffer(1);
		GL15.glGenBuffers(indexID);
		this.box.setIndexVBO(indexID.get(0));
		// Directly load in data since indices do not change.
		// Bind index buffer to make it active.
		GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, this.box.getIndexVBO());
		// Send index buffer data to card.
		GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, this.box.getIndexBuffer(), GL15.GL_STATIC_DRAW);
	}
	
	private void initTexture() {
		// Generate texture id.
		long start = System.currentTimeMillis();
		IntBuffer id = BufferUtils.createIntBuffer(1);
		GL11.glGenTextures(id);
		// Bind texture type and id.
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, id.get(0));
		// Set alignment.
		GL11.glPixelStoref(GL11.GL_UNPACK_ALIGNMENT, 1);
		try {
			BufferedImage image = ImageIO.read(this.url);
			// Convert image data to byte buffer.
			byte data[] = (byte[])image.getRaster().getDataElements(0, 0,image.getWidth(), image.getHeight(), null);
			ByteBuffer buffer = BufferUtils.createByteBuffer(data.length);
			buffer.clear();
			buffer.put(data);
			buffer.flip();
			buffer.rewind();
			// Linear filtering for minification.
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
			// Linear filtering for magnification.
			GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
			// Send image to VRAM.
			GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB8, image.getWidth(), image.getHeight(), 0, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, buffer);
			long end = System.currentTimeMillis();
			System.out.println("Texture loading took: " + (end-start) + "ms");
		} catch (IOException e) {
			e.printStackTrace();
		}
		// Store texture id.
		this.textureID = id.get(0);
	}

	@Override
	public void render() {
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, this.textureID);
		GL12.glDrawRangeElements(GL11.GL_TRIANGLE_STRIP, 0, this.box.getMaxIndex(), this.box.getIndexCount(), GL11.GL_UNSIGNED_INT, 0);
		Display.update();
	}

	@Override
	public boolean activate(final IResourceRetriever handler) throws Exception {
		this.url = handler.getResource("texture.jpg");
		return true;
	}

	@Override
	public boolean connect(final IComponent unit, final String interfaceName) throws Exception {
		return true;
	}

	@Override
	public void deactivate() throws Exception {}
}
